package com.zxy.supplier_system.server.service

import com.zxy.supplier_system.server.controller.GoodsController
import com.zxy.supplier_system.server.controller.ProcurementController
import com.zxy.supplier_system.server.controller.ProcurementsItemController
import com.zxy.supplier_system.server.controller.ProcurementsOrderController
import com.zxy.supplier_system.server.entity.*
import com.zxy.supplier_system.server.repository.*
import com.zxy.supplier_system.server.utils.*
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice
import org.springframework.data.jpa.domain.Specification
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import java.math.BigDecimal
import java.time.OffsetDateTime

@Service
class ProcurementService(
    private val procurementSupplierRepository: ProcurementSupplierRepository,
    private val procurementItemRepository: ProcurementItemRepository,
    private val employeeRepository: EmployeeRepository,
    private val procurementRepository: ProcurementRepository,
    private val systemServiceService: SystemServiceService,
    private val goodsRepository: GoodsRepository,
    private val employeeOperateRecordService: EmployeeOperateRecordService,
    private val procurementReceiptRepository: ProcurementReceiptRepository,
    private val goodsUnitRepository: GoodsUnitRepository,
    private val employeeService: EmployeeService,
) {

    private val orderNumberGenerator = OrderNumberGenerator(initialCurrentSequence = {
        procurementRepository.countToday()
    })

    @Transactional(readOnly = true)
    fun queryProcurement(
        queryProcurementRequest: ProcurementController.QueryProcurementRequest,
        pageable: Pageable
    ): Page<ProcurementController.QueryProcurementResponse> {

        return this.procurementRepository.findAll(
            Specification.allOf(
                ProcurementRepository.Specifications.supplierIn(queryProcurementRequest.supplierIds),
                ProcurementRepository.Specifications.endDateLessThanOrEqualTo(queryProcurementRequest.endDate),
                ProcurementRepository.Specifications.startDateGreaterThanOrEqualTo(queryProcurementRequest.startDate),
                ProcurementRepository.Specifications.createdByIn(queryProcurementRequest.createdByIds),
                ProcurementRepository.Specifications.statusEqual(queryProcurementRequest.status),
            ), pageable
        ).map { procurement ->
            ProcurementController.QueryProcurementResponse(
                procurement.id,
                procurement.createdBy.let {
                    IntIdAndName(it.id!!, it.name)
                },
                procurement.createdDateTime,
                procurement.items.size,
                procurement.supplier.let {
                    IntIdAndName(it.id, it.name)
                },
                procurement.status,
                procurement.receipt?.id,
                procurement.note,
                procurement.items.totalPurchasePrice,
            )
        }
    }

    @Transactional
    fun createProcurement(supplierId: Int, request: ProcurementsOrderController.AddProcurementRequest): Int {
        val procurement = this.procurementRepository.save(Procurement().apply {
            this.supplier = procurementSupplierRepository.findByIdOrNull(supplierId).orElse404()
            this.number = orderNumberGenerator.generate()
            this.createdDateTime = request.createdDateTime ?: OffsetDateTime.now()
        })
        this.employeeOperateRecordService.createProcurement(procurement)
        return procurement.id
    }

    @Transactional
    fun updateOrderGoods(
        id: Int,
        updateOrderGoodsRequest: ProcurementController.UpdateOrderGoodsRequest
    ) {
        val procurement = this.procurementRepository.findByIdOrNull(id).orElse404()
        procurement.items.clear()
        procurement.items.addAll(
            updateOrderGoodsRequest.items.mapIndexed { index, it ->
                ProcurementItem().apply {
                    this.procurement = procurement
                    this.amount = it.amount
                    this.goods =
                        goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(it.id, systemServiceService.getCurrentSystemService())
                            .orElse404()
                    this.sequence = index
                }
            }
        )
        this.procurementRepository.save(procurement)
    }

    @Transactional(readOnly = true)
    fun getProcurementDetail(id: Int, includeItems: Boolean): ProcurementController.ProcurementDetail {
        val procurement = this.procurementRepository.findByIdOrNull(id).orElse404()
        return ProcurementController.ProcurementDetail(
            procurement.id,
            procurement.createdDateTime,
            IntIdAndName(procurement.createdBy.id!!, procurement.createdBy.name),
            IntIdAndName(procurement.supplier.id, procurement.supplier.name),
            if (!includeItems) emptyList() else procurement.items.map { item ->
                val goods = item.goods
                goods.let { it ->
                    ProcurementController.ProcurementItemResponse(
                        it.id,
                        it.name, it.barcode,
                        it.price,
                        it.amount,
                        it.createdDateTime,
                        it.lastModifiedDateTime,
                        it.brand?.toIntIdAndName(),
                        it.category?.toIntIdAndName(),
                        it.factory?.toIntIdAndName(),
                        item.amount,
                        it.boxSpecification,
                        item.unit?.toLongIdAndName(),
                        it.units.map {
                            QueryGoodsUnitResponse.formGoodsUnit(it)
                        },
                        item.subGoods?.let(GoodsController.GoodsDetail.SubGoodsResponse::fromSubGoods),
                        item.note,
                        item.purchasePrice,
                    )
                }
            },
            procurement.submittedDateTime,
            procurement.procurementReceipt?.createdDateTime,
            procurement.receipt?.id,
            procurement.note,
        )
    }

    @Transactional
    fun addItem(orderId: Int, addProcurementsItemRequest: ProcurementsItemController.AddProcurementsItemRequest) {
        val procurement = this.procurementRepository.findByIdOrNull(orderId).orElse404()
        if (procurement.items.any { it.goods.id == addProcurementsItemRequest.goodsId && it.subGoods?.id == addProcurementsItemRequest.subGoodsId })
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "EXISTED")
        procurement.items.add(ProcurementItem().apply {
            this.procurement = procurement
            this.goods = goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(
                addProcurementsItemRequest.goodsId,
                systemServiceService.getCurrentSystemService()
            ).orElse404()
            this.subGoods =
                if (addProcurementsItemRequest.subGoodsId != null)
                    this.goods.subGoodsList.find { it.id == addProcurementsItemRequest.subGoodsId }.orElse404()
                else
                    null
            this.amount = addProcurementsItemRequest.amount
            this.sequence = procurement.items.nextSequence
            this.note = addProcurementsItemRequest.note
            this.purchasePrice = addProcurementsItemRequest.purchasePrice
            this.unit = addProcurementsItemRequest.unitId?.let {
                goodsUnitRepository.findByIdOrNull(it)
            }
        })
        this.procurementRepository.save(procurement)
        this.employeeOperateRecordService.addProcurementItem(procurement)
    }

    @Transactional
    fun deleteItem(orderId: Int, goodsId: Int) {
        // TODO employee record
        val procurementItem = this.procurementItemRepository.findByProcurement_IdAndGoods_Id(orderId, goodsId)
        if (procurementItem != null) {
            this.procurementItemRepository.delete(procurementItem)
            this.employeeOperateRecordService.deleteProcurementItem(procurementItem)
        }
    }

    @Transactional
    fun deleteItem(orderId: Int, goodsId: Int, subGoodsId: Int) {
        val procurementItem =
            this.procurementItemRepository.findByProcurement_IdAndGoods_IdAndSubGoods_Id(orderId, goodsId, subGoodsId)
        if (procurementItem != null) {
            this.procurementItemRepository.delete(procurementItem)
            this.employeeOperateRecordService.deleteProcurementItem(procurementItem)
        }
    }

    @Transactional
    fun updateItemAmount(
        goodsId: Int,
        orderId: Int,
        updateItemAmountRequest: ProcurementsItemController.UpdateItemAmountRequest
    ) {
        val procurementItem =
            this.procurementItemRepository.findByProcurement_IdAndGoods_Id(orderId, goodsId).orElse404()
        procurementItem.amount = updateItemAmountRequest.amount
        procurementItem.unit = updateItemAmountRequest.unitId?.let {
            this.goodsUnitRepository.findByIdOrNull(it)
        }
        this.procurementItemRepository.save(procurementItem)
        this.employeeOperateRecordService.updateProcurementItemAmount(procurementItem)
    }

    @Transactional
    fun updateItemAmount(
        goodsId: Int,
        orderId: Int,
        subGoodsId: Int,
        updateItemAmountRequest: ProcurementsItemController.UpdateItemAmountRequest
    ) {
        val procurementItem =
            this.procurementItemRepository.findByProcurement_IdAndGoods_IdAndSubGoods_Id(orderId, goodsId, subGoodsId)
                .orElse404()
        procurementItem.amount = updateItemAmountRequest.amount
        procurementItem.unit = updateItemAmountRequest.unitId?.let {
            this.goodsUnitRepository.findByIdOrNull(it)
        }
        this.procurementItemRepository.save(procurementItem)
        this.employeeOperateRecordService.updateProcurementItemAmount(procurementItem)
    }

    @Transactional
    fun updateItemNote(goodsId: Int, orderId: Int, note: String) {
        val procurementItem =
            this.procurementItemRepository.findByProcurement_IdAndGoods_Id(orderId, goodsId)
                .orElse404()
        procurementItem.note = note
        this.procurementItemRepository.save(procurementItem)
        this.employeeOperateRecordService.updateProcurementItemNote(procurementItem)
    }

    @Transactional
    fun updateItemNote(goodsId: Int, orderId: Int, subGoodsId: Int, note: String) {
        val procurementItem =
            this.procurementItemRepository.findByProcurement_IdAndGoods_IdAndSubGoods_Id(orderId, goodsId, subGoodsId)
                .orElse404()
        procurementItem.note = note
        this.procurementItemRepository.save(procurementItem)
        this.employeeOperateRecordService.updateProcurementItemNote(procurementItem)
    }

    fun getAllEmployees(): List<IntIdAndName> {
        return this.employeeRepository.findAll(
            EmployeeRepository.Specifications.haveProcurement().and(
                SystemServiceRepository.Specifications.systemServiceEquals(
                    { root -> root },
                    systemServiceService.getCurrentSystemService()
                )
            )
        ).map {
            IntIdAndName(it.id!!, it.name)
        }
    }

    @Transactional
    fun queryProcurementInSupplier(
        supplierId: Int,
        pageable: Pageable
    ): Slice<ProcurementsOrderController.QueryProcurementInSupplierResponse> {
        return this.procurementRepository.findAllBySupplier_Id(supplierId, pageable).map {
            ProcurementsOrderController.QueryProcurementInSupplierResponse(
                it.id,
                it.createdBy.toIntIdAndName(),
                it.createdDateTime,
                it.items.size,
                it.submittedDateTime,
                it.items.totalPurchasePrice,
            )
        }
    }

    fun queryOrderItemsInOrder(
        orderId: Int,
        pageable: Pageable
    ): Page<ProcurementsItemController.OrderItemInOrderSummary> {
        return this.procurementItemRepository.findAllByProcurement_IdOrderBySequenceAsc(orderId, pageable).map {
            val goods = it.goods
            ProcurementsItemController.OrderItemInOrderSummary.fromGoodsAndAmountAndUnit(
                goods,
                it.amount,
                it.unit,
                it.subGoods
            )
        }
    }

    fun exists(orderId: Int, goodsId: Int): Boolean {
        return this.procurementItemRepository.existsByProcurement_IdAndGoods_Id(orderId, goodsId)
    }

    fun exists(orderId: Int, barcode: String): Boolean {
        return this.procurementItemRepository.existsByProcurement_IdAndGoods_Barcode(orderId, barcode)
    }


    @Transactional
    fun addItem(orderId: Int, barcode: String): ProcurementsItemController.OrderItemInOrderSummary {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val procurement = this.procurementRepository.findByIdAndSupplier_SystemService(
            orderId,
            currentSystemService
        ).orElse404()
        if (procurement.items.any { it.goods.barcode == barcode && it.subGoods == null })
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "EXISTED")
        val goods =
            goodsRepository.findByBarcodeAndSystemServiceAndDisableIsFalse(barcode, currentSystemService)
                .orElse404()
        return orderItemInOrderSummary(procurement, goods)
    }

    @Transactional
    fun submitProcurement(id: Int) {
        val procurement = this.procurementRepository.findByIdAndSupplier_SystemService(
            id,
            systemServiceService.getCurrentSystemService()
        ).orElse404()
        if (procurement.submittedDateTime == null) {
            procurement.submittedDateTime = OffsetDateTime.now()
            this.procurementRepository.save(procurement)
            this.employeeOperateRecordService.submitProcurement(procurement)
        } else {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST)
        }
    }

    @Transactional
    fun addItem(orderId: Int, goodsId: Int): ProcurementsItemController.OrderItemInOrderSummary {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val procurement = this.procurementRepository.findByIdAndSupplier_SystemService(
            orderId,
            currentSystemService
        ).orElse404()
        if (procurement.items.any { it.goods.id == goodsId && it.subGoods == null })
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "EXISTED")
        val goods =
            goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(goodsId, currentSystemService)
                .orElse404()
        return orderItemInOrderSummary(procurement, goods)
    }

    private fun orderItemInOrderSummary(
        procurement: Procurement,
        goods: Goods,
        subGoods: SubGoods? = null
    ): ProcurementsItemController.OrderItemInOrderSummary {
        val latestItem =
            getLatestItemByProcurementAndItemKey(procurement, goods, subGoods)
        val amount = latestItem?.amount ?: 1
        val unit = latestItem?.unit
        val goodsUnit = unit ?: goods.basicUnit
        val purchasePrice = latestItem?.purchasePrice ?: goods.costPrice ?: BigDecimal.ZERO
        procurement.items.add(ProcurementItem().apply {
            this.procurement = procurement
            this.goods =
                goods
            this.amount = amount
            this.subGoods = subGoods
            this.sequence = procurement.items.nextSequence
            this.purchasePrice = purchasePrice
            this.unit = goodsUnit
        })
        this.procurementRepository.save(procurement)
        this.employeeOperateRecordService.addProcurementItem(procurement)
        return ProcurementsItemController.OrderItemInOrderSummary.fromGoodsAndAmountAndUnit(
            goods,
            amount,
            goodsUnit,
            subGoods,
            purchasePrice
        )
    }

    private fun getLatestItemByProcurementAndItemKey(
        procurement: Procurement,
        goods: Goods,
        subGoods: SubGoods? = null
    ): ProcurementItem? {
        val supplier = procurement.supplier
        return getLatestItemByProcurementAndItemKey(supplier, goods, subGoods)
    }

    fun getLatestItemByProcurementAndItemKey(
        supplier: ProcurementSupplier,
        goods: Goods,
        subGoods: SubGoods?
    ): ProcurementItem? {
        val latestItem =
            if (subGoods == null)
                this.procurementItemRepository.findFirstByProcurement_Supplier_IdAndGoods_IdOrderByProcurement_CreatedDateTimeDesc(
                    supplier.id,
                    goods.id
                )
            else
                this.procurementItemRepository.findFirstByProcurement_Supplier_IdAndGoods_IdAndSubGoods_IdOrderByProcurement_CreatedDateTimeDesc(
                    supplier.id,
                    goods.id,
                    subGoods.id!!
                )
        return latestItem
    }

    fun getLatestItemPurchaseByProcurementAndItemKey(
        supplier: ProcurementSupplier, goods: Goods,
        subGoods: SubGoods? = null
    ): BigDecimal? {
        return getLatestItemByProcurementAndItemKey(
            supplier,
            goods,
            subGoods
        )?.purchasePrice ?: (if (subGoods != null) getLatestItemByProcurementAndItemKey(
            supplier,
            goods,
            null
        )?.purchasePrice else null)
    }

    fun getLatestItemPurchaseByProcurementAndItemKey(
        procurement: Procurement,
        goods: Goods,
        subGoods: SubGoods? = null
    ): BigDecimal? {
        return getLatestItemByProcurementAndItemKey(
            procurement.supplier,
            goods,
            subGoods
        )?.purchasePrice ?: (if (subGoods != null) getLatestItemByProcurementAndItemKey(
            procurement.supplier,
            goods,
            null
        )?.purchasePrice else null)
    }


    @Transactional
    fun receipt(id: Int): ProcurementReceipt {
        val procurement = this.procurementRepository.findByIdAndSupplier_SystemService(
            id,
            systemServiceService.getCurrentSystemService()
        ).orElse404()
        if (procurement.submittedDateTime == null) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "订单未提交")
        }
        var procurementReceipt = procurementReceiptRepository.save(ProcurementReceipt().apply {
            this.supplier = procurement.supplier
            this.procurement = procurement
            this.createdBy = employeeService.getRequiredCurrentEmployee()
        })
        procurementReceipt = procurementReceipt.apply {
            this.items = procurement.items.map { procurementItem ->
                ProcurementReceiptItem().apply {
                    this.procurementReceipt = procurementReceipt
                    this.amount = procurementItem.amount
                    this.goods = procurementItem.goods
                    this.unit = procurementItem.unit
                    this.subGoods = procurementItem.subGoods
                    this.purchasePrice = procurementItem.purchasePrice
                }
            }.toMutableList()
        }
        procurementReceipt = this.procurementReceiptRepository.save(procurementReceipt)
        this.employeeOperateRecordService.receipt(procurement)

        return procurementReceipt
    }

    @Transactional
    fun importFromExcelItems(
        orderId: Int,
        items: List<ProcurementsItemController.ProcurementItemExcelItem>
    ): Int {
        val procurement = this.procurementRepository.findByIdAndSupplier_SystemService(
            orderId,
            systemServiceService.getCurrentSystemService()
        ).orElse404()

        val procurementItems = items.distinctBy { it.barcode + it.subGoodsNumber }
            .filterNot { item ->
                procurement.items.any {
                    it.goods.barcode == item.barcode && it.subGoods?.number == item.subGoodsNumber
                }
            }.mapIndexedNotNull { index, item ->
                ProcurementItem().apply {
                    this.sequence = procurement.items.nextSequence + index
                    val goods = goodsRepository.findByBarcodeAndSystemServiceAndDisableIsFalse(
                        item.barcode,
                        systemServiceService.getCurrentSystemService()
                    ).orElse404()
                    this.goods = goods
                    this.subGoods = if (item.subGoodsNumber == null)
                        null
                    else
                        goods.subGoodsList.find { subGoods -> subGoods.number == item.subGoodsNumber }
                            ?: return@mapIndexedNotNull null
                    this.amount = item.amount
                    this.unit = goods.units.find { it.name == item.unit }
                    this.purchasePrice =
                        item.purchasePrice ?: getLatestItemPurchaseByProcurementAndItemKey(procurement, goods, subGoods)
                                ?: goods.costPrice ?: BigDecimal.ZERO
                    this.note = item.note
                    this.procurement = procurement
                }

            }
        this.procurementItemRepository.saveAll(procurementItems)
        return procurementItems.size
    }

    @Transactional
    fun updateProcurementNote(
        id: Int,
        request: ProcurementController.UpdateProcurementNoteRequest
    ) {
        val procurement = this.procurementRepository.findByIdOrNull(id).orElse404()
        procurement.note = request.note
        this.procurementRepository.save(procurement)
    }

    @Transactional
    fun batchAddItems(
        orderId: Int,
        itemKeys: List<ProcurementsItemController.AddProcurementItemKey>
    ): List<ProcurementsItemController.OrderItemInOrderSummary> {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val marketOrder = this.procurementRepository.findByIdAndSupplier_SystemService(
            orderId,
            currentSystemService
        ).orElse404()
        val goodsList =
            goodsRepository.findByIdInAndSystemServiceAndDisableIsFalse(itemKeys.map { it.goodsId }, currentSystemService)
                .orElse404()
        return itemKeys.map { key ->
            val goods = goodsList.find { it.id == key.goodsId }.orElse404()
            orderItemInOrderSummary(marketOrder, goods, goods.subGoodsList.find { key.subGoodsId == it.id })
        }
    }

    @Transactional
    fun delete(id: Int): Unit {
        this.procurementRepository.deleteByIdAndSupplier_SystemService(
            id,
            systemServiceService.getCurrentSystemService()
        )
    }

    @Transactional
    fun updateItemPurchasePrice(
        goodsId: Int,
        procurementId: Int,
        request: ProcurementsItemController.UpdatePurchasePriceRequest
    ) {
        val procurementItem =
            this.procurementItemRepository.findByProcurement_IdAndGoods_Id(procurementId, goodsId).orElse404()
        procurementItem.purchasePrice = request.purchasePrice
        this.procurementItemRepository.save(procurementItem)
        this.employeeOperateRecordService.updateProcurementItemPurchasePrice(procurementItem)
    }

    @Transactional
    fun updateItemPurchasePrice(
        goodsId: Int,
        procurementId: Int,
        subGoodsId: Int,
        request: ProcurementsItemController.UpdatePurchasePriceRequest
    ) {
        val procurementItem =
            this.procurementItemRepository.findByProcurement_IdAndGoods_IdAndSubGoods_Id(
                procurementId,
                goodsId,
                subGoodsId
            ).orElse404()
        procurementItem.purchasePrice = request.purchasePrice
        this.procurementItemRepository.save(procurementItem)
        this.employeeOperateRecordService.updateProcurementItemPurchasePrice(procurementItem)
    }
}